
/* GCSx
** OPENGL.CPP
**
** Core OpenGL functions
*/

/*****************************************************************************
** Copyright (C) 2003-2006 Janson
**
** This program is free software; you can redistribute it and/or modify
** it under the terms of the GNU General Public License as published by
** the Free Software Foundation; either version 2 of the License, or
** (at your option) any later version.
** 
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
** GNU General Public License for more details.
**
** You should have received a copy of the GNU General Public License
** along with this program; if not, write to the Free Software
** Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111, USA.
*****************************************************************************/

#include "all.h"

int alphaOn = 0;
int blendOn = 0;

void oglInit(int width, int height) { start_func
    oglError("oglInit: selectVideoMode");
    oglDebug(glViewport(0, 0, width, height));
    oglDebug(glClearColor(0.0f, 0.0f, 0.0f, 0.0f));
    oglDebug(glEnable(GL_TEXTURE_2D));
    oglDebug(glDisable(GL_DEPTH_TEST));
    oglDebug(glAlphaFunc(GL_GREATER, 0));
    oglDebug(glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA));
    oglDebug(glDisable(GL_ALPHA_TEST));
    oglDebug(glDisable(GL_BLEND));

    alphaOn = 0;
    blendOn = 0;

    // May change later
    oglDebug(glShadeModel(GL_SMOOTH));

    // Change to modulate to allow coloring
    oglDebug(glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE));

    oglDebug(glMatrixMode(GL_PROJECTION));
    oglDebug(glLoadIdentity());
    oglDebug(glOrtho(0, width, height, 0, 0, 1));
    oglDebug(glMatrixMode(GL_MODELVIEW));
    oglError("oglInit");
    
    // Some places optimize output and need to reset when GL is initialized
    TextureMap::setupOptimizations();
}

void oglAlphaMode(int transparency, int fullAlpha) { start_func
    if (fullAlpha) {
        if (!blendOn) {
            oglDebug(glEnable(GL_BLEND));
            blendOn = 1;
        }
    }
    else {
        if (blendOn) {
            oglDebug(glDisable(GL_BLEND));
            blendOn = 0;
        }
    }
    if ((transparency) && (!fullAlpha)) {
        if (!alphaOn) {
            oglDebug(glEnable(GL_ALPHA_TEST));
            alphaOn = 1;
        }
    }
    else {
        if (alphaOn) {
            oglDebug(glDisable(GL_ALPHA_TEST));
            alphaOn = 0;
        }
    }
}

int oglTestPossibleTextures(int width, int height, int withAlpha, int limit) { start_func
    // Test data-
    Uint8* testdata = new Uint8[width * height * (withAlpha ? 4 : 3)];
    Uint8* writeTo = testdata;
    for (int pos = width * height * (withAlpha ? 4 : 3); pos > 0; --pos)
        *writeTo++ = rand();

    // Limit to 8 seconds
    long long ticks = SDL_GetTicks();
    int time;
    // Add texture storage in powers of 2
    int allocSize = 64;
    GLuint* textures = new GLuint[allocSize];
    GLboolean* residencies = new GLboolean[allocSize];
    // Add textures 8 at a time
    int pos = 0;
    int found = 0;
    GLint getData;
    while (pos <= limit) {
        // Allocate more space?
        if (pos >= allocSize) {
            GLuint* newTextures = new GLuint[allocSize * 2];
            GLboolean* newResidencies = new GLboolean[allocSize * 2];
            memcpy(newTextures, textures, sizeof(GLuint[allocSize]));
            memcpy(newResidencies, residencies, sizeof(GLboolean[allocSize]));
            delete[] textures;
            delete[] residencies;
            textures = newTextures;
            residencies = newResidencies;
            allocSize *= 2;
        }
        // Create new texture
        oglDebug(glGenTextures(8, textures + pos));
        for (int subpos = 0; subpos < 8; ++subpos) {
            oglDebug(glBindTexture(GL_TEXTURE_2D, textures[pos]));
            oglDebug(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST));
            oglDebug(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST));
            residencies[pos] = GL_TRUE;
            oglDebug(glTexImage2D(GL_TEXTURE_2D, 0, withAlpha ? GL_RGBA : GL_RGB, width, height, 0,
                                  withAlpha ? GL_RGBA : GL_RGB, GL_UNSIGNED_BYTE, testdata));

            // Verify storage
            oglDebug(glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_WIDTH, &getData));
            if (getData != width) {
                found = 1;
                break;
            }
            // Draw with texture
            glBegin(GL_QUADS);
            glTexCoord2f(0.0, 0.0); glVertex3i(0, 0, 0);
            glTexCoord2f(1.0, 0.0); glVertex3i(width, 0, 0);
            glTexCoord2f(1.0, 1.0); glVertex3i(width, height, 0);
            glTexCoord2f(0.0, 1.0); glVertex3i(0, height, 0);
            oglDebug(glEnd());

            ++pos;
        }

        if (found) break;

        // Verify residency of ALL textures
        if (glAreTexturesResident(pos, textures, residencies) == GL_FALSE) {
            found = 1;
            break;
        }
        
        if (SDL_GetTicks() - ticks > 8000) {
            time = 1;
            break;
        }
    }

    oglDebug(glDeleteTextures(pos, textures));
    delete[] textures;
    delete[] residencies;
    delete[] testdata;   
    oglError("oglTestPossibleTextures");
    
    if (found) {
        if (withAlpha) {
            debugWrite("Maximum %dx%d RGBA textures resident: %d", width, height, pos - 8);
        }
        else {
            debugWrite("Maximum %dx%d RGB textures resident: %d", width, height, pos - 8);
        }
        return pos - 1;
    }
    else {
        if (withAlpha) {
            debugWrite("Maximum %dx%d RGBA textures resident: over %d", width, height, pos - 8);
        }
        else {
            debugWrite("Maximum %dx%d RGB textures resident: over %d", width, height, pos - 8);
        }
        if (time) {
            debugWrite("(test aborted due to time)");
        }
        return -1;
    }
}

void oglTestCapabilities() { start_func
    GLint result;

    // Reported capabilities
    oglDebug(glGetIntegerv(GL_DOUBLEBUFFER, &result));
    if (result == GL_TRUE) {
        debugWrite(DEBUG_VIDEO, "Double-buffering: Supported");
    }
    else {
        debugWrite(DEBUG_VIDEO, "Double-buffering: Not supported");
    }
    oglDebug(glGetIntegerv(GL_MAX_TEXTURE_SIZE, &result));
    debugWrite(DEBUG_VIDEO, "Stated max texture size: %d", result);
    int maxSize = result;
    int minSize = 1;

    // Report on min/max sizes
    int size;
    for (int withAlpha = 0; withAlpha <= 1; ++withAlpha) {
        int found = 0;
        for (size = 64; size >= 1; size /= 2) {
            oglDebug(glTexImage2D(GL_PROXY_TEXTURE_2D, 0, withAlpha ? GL_RGBA : GL_RGB, size, size, 0,
                                  withAlpha ? GL_RGBA : GL_RGB, GL_UNSIGNED_BYTE, NULL));
            // Verify storage
            oglDebug(glGetTexLevelParameteriv(GL_PROXY_TEXTURE_2D, 0, GL_TEXTURE_WIDTH, &result));
            if (result != size) {
                found = 1;
                break;
            }
            oglDebug(glGetTexLevelParameteriv(GL_PROXY_TEXTURE_2D, 0, GL_TEXTURE_HEIGHT, &result));
            if (result != size) {
                found = 1;
                break;
            }
        }
        if (found) size *= 2;
        else size = 1;
        if (withAlpha) {
            debugWrite(DEBUG_VIDEO, "Min texture size with alpha: %d", size);
        }
        else {
            debugWrite(DEBUG_VIDEO, "Min texture size, no alpha: %d", size);
        }
        if (size > minSize) minSize = size;

        found = 0;
        for (size = 64; size <= 65536; size *= 2) {
            oglDebug(glTexImage2D(GL_PROXY_TEXTURE_2D, 0, withAlpha ? GL_RGBA : GL_RGB, size, size, 0,
                                  withAlpha ? GL_RGBA : GL_RGB, GL_UNSIGNED_BYTE, NULL));
            // Verify storage
            oglDebug(glGetTexLevelParameteriv(GL_PROXY_TEXTURE_2D, 0, GL_TEXTURE_WIDTH, &result));
            if (result != size) {
                found = 1;
                break;
            }
            oglDebug(glGetTexLevelParameteriv(GL_PROXY_TEXTURE_2D, 0, GL_TEXTURE_HEIGHT, &result));
            if (result != size) {
                found = 1;
                break;
            }
        }
        if (found) {
            size /= 2;
            if (withAlpha) {
                debugWrite(DEBUG_VIDEO, "Max texture size with alpha: %d", size);
            }
            else {
                debugWrite(DEBUG_VIDEO, "Max texture size, no alpha: %d", size);
            }
        }
        else {
            if (withAlpha) {
                debugWrite(DEBUG_VIDEO, "Max texture size with alpha: over 65536");
            }
            else {
                debugWrite(DEBUG_VIDEO, "Max texture size, no alpha: over 65536");
            }
        }
        // Track min between the three
        if (size < maxSize) maxSize = size;
    }
    
    // Attempt non-square texture
    oglDebug(glTexImage2D(GL_PROXY_TEXTURE_2D, 0, GL_RGBA, maxSize,
                          maxSize / 2, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL));
    // Clear error status (an error is expected here)
    while (glGetError()) ;
    // Verify storage
    oglDebug(glGetTexLevelParameteriv(GL_PROXY_TEXTURE_2D, 0, GL_TEXTURE_WIDTH, &result));
    if (result == maxSize) {
        oglDebug(glGetTexLevelParameteriv(GL_PROXY_TEXTURE_2D, 0, GL_TEXTURE_HEIGHT, &result));
        if (result == maxSize / 2) {
            debugWrite(DEBUG_VIDEO, "Non-square textures: Supported");
        }
        else {
            debugWrite(DEBUG_VIDEO, "Non-square textures: Not supported");
        }
    }
    else {
        debugWrite(DEBUG_VIDEO, "Non-square textures: Not supported");
    }
    
    // Attempt non-^2 texture
    glTexImage2D(GL_PROXY_TEXTURE_2D, 0, GL_RGBA, 100, 100, 0,
                 GL_RGBA, GL_UNSIGNED_BYTE, NULL);
    // Clear error status (an error is expected here)
    while (glGetError()) ;
    // Verify storage
    oglDebug(glGetTexLevelParameteriv(GL_PROXY_TEXTURE_2D, 0, GL_TEXTURE_WIDTH, &result));
    oglError("oglTestCapabilities");
    if (result == 100) {
        debugWrite(DEBUG_VIDEO, "Non-^2 textures: Supported");
    }
    else {
        debugWrite(DEBUG_VIDEO, "Non-^2 textures: Not supported");
    }

    // Always force ^2 textures as some cards support non-^2 textures but VERY SLOWLY
    config->write(OGL_POWER_OF_TWO, 1);
    config->write(OGL_MIN_SIZE, minSize);
    config->write(OGL_MAX_SIZE, maxSize);
}

void oglError(const char* note) { start_func
    const char* errStr;
    while (GLenum err = glGetError()) {
        switch (err) {
            case GL_INVALID_ENUM:
                errStr = "Invalid Enum";
                break;
            case GL_INVALID_VALUE:
                errStr = "Invalid Value";
                break;
            case GL_INVALID_OPERATION:
                errStr = "Invalid Operation";
                break;
            case GL_STACK_OVERFLOW:
                errStr = "Stack Overflow";
                break;
            case GL_STACK_UNDERFLOW:
                errStr = "Stack Underflow";
                break;
            case GL_OUT_OF_MEMORY:
                errStr = "Out of Memory";
                break;
        }
        debugWrite(DEBUG_VIDEO, "%s: GL Error: %s", note, errStr);
    }
}
